#include "kernel.h"
#include "errno.h"
#include "globals.h"

#include "vm/vmmap.h"
#include "vm/shadow.h"
#include "vm/anon.h"

#include "proc/proc.h"

#include "util/debug.h"
#include "util/list.h"
#include "util/string.h"
#include "util/printf.h"

#include "fs/vnode.h"
#include "fs/file.h"
#include "fs/fcntl.h"
#include "fs/vfs_syscall.h"

#include "mm/slab.h"
#include "mm/page.h"
#include "mm/mm.h"
#include "mm/mman.h"
#include "mm/mmobj.h"
#include "limits.h"

static slab_allocator_t *vmmap_allocator;
static slab_allocator_t *vmarea_allocator;

void
vmmap_init(void)
{
        vmmap_allocator = slab_allocator_create("vmmap", sizeof(vmmap_t));
        KASSERT(NULL != vmmap_allocator && "failed to create vmmap allocator!");
        vmarea_allocator = slab_allocator_create("vmarea", sizeof(vmarea_t));
        KASSERT(NULL != vmarea_allocator && "failed to create vmarea allocator!");
}

vmarea_t *
vmarea_alloc(void)
{
        vmarea_t *newvma = (vmarea_t *) slab_obj_alloc(vmarea_allocator);
        if (newvma) {
                newvma->vma_vmmap = NULL;
        }
        return newvma;
}

void
vmarea_free(vmarea_t *vma)
{
        KASSERT(NULL != vma);
        slab_obj_free(vmarea_allocator, vma);
}

/* Create a new vmmap, which has no vmareas and does
 * not refer to a process. */
vmmap_t *
vmmap_create(void)
{
/***************Aahlad********************************/
	vmmap_t* vm_map = (vmmap_t*)slab_obj_alloc(vmmap_allocator);
	list_init(&vm_map->vmm_list);
	return vm_map;
/******************End********************************/
}

/* Removes all vmareas from the address space and frees the
 * vmmap struct. */
void
vmmap_destroy(vmmap_t *map)
{
	KASSERT(NULL != map);
dbg(DBG_PRINT,"Entering vmmap_destroy\n");
	/*Just call vmmap_remove on whole address range*/
	vmmap_remove(map,0,UINT_MAX);
	KASSERT(list_empty(&map->vmm_list) && "map area list must be empty after remove all");
        slab_obj_free(vmmap_allocator, map);
	dbg(DBG_PRINT, "Exiting vmmap_destroy\n");
}

/* Add a vmarea to an address space. Assumes (i.e. asserts to some extent)
 * the vmarea is valid.  This involves finding where to put it in the list
 * of VM areas, and adding it. Don't forget to set the vma_vmmap for the
 * area. */
void
vmmap_insert(vmmap_t *map, vmarea_t *newvma)
{
/***********Aahlad********************/
KASSERT(NULL != map && NULL != newvma);
 KASSERT(NULL == newvma->vma_vmmap);
KASSERT(newvma->vma_start < newvma->vma_end);
 KASSERT(ADDR_TO_PN(USER_MEM_LOW) <= newvma->vma_start && ADDR_TO_PN(USER_MEM_HIGH) >= newvma->vma_end);	
	dbg(DBG_PRINT,"Entering vmmap_insert operation\n"); 
	vmarea_t *vma, *next_vma, *prev_vma;
	list_link_t *nlink;
	list_iterate_begin(&map->vmm_list,vma,vmarea_t,vma_plink){

		nlink = map->vmm_list.l_next;
		next_vma = list_item(nlink,vmarea_t,vma_plink);
		if(newvma->vma_start > vma->vma_end && newvma->vma_end < next_vma->vma_start)
		{
			list_insert_before(&vma->vma_plink, &newvma->vma_plink);
		}
	
	}list_iterate_end();

	newvma->vma_vmmap = map;      
	dbg(DBG_PRINT,"Exitng vmmap_insert : vmarea successfully inserted into the map\n"); 
/************************************/
}

/* Find a contiguous range of free virtual pages of length npages in
 * the given address space. Returns starting vfn for the range,
 * without altering the map. Returns -1 if no such range exists.
 *
 * Your algorithm should be first fit. If dir is VMMAP_DIR_HILO, you
 * should find a gap as high in the address space as possible; if dir
 * is VMMAP_DIR_LOHI, the gap should be as low as possible. */
int
vmmap_find_range(vmmap_t *map, uint32_t npages, int dir)
{
/*******Aahlad and Ishita *****************/
	KASSERT(NULL != map);
	KASSERT(0 < npages);
	dbg(DBG_PRINT,"entering vmmap_find_range\n");
	uint32_t gap=0, free_sp=0, lgap = 10000000;
	vmarea_t *vma,*next_vma;
	list_link_t *link;
	
	list_iterate_begin(&map->vmm_list,vma,vmarea_t,vma_plink){
	link = map->vmm_list.l_next;
	next_vma=list_item(link,vmarea_t,vma_plink);
	free_sp=next_vma->vma_start - vma->vma_end -1;	
	if(dir == VMMAP_DIR_HILO)
	{

	if(free_sp - npages > gap)
		{
		gap = free_sp - npages;
		}
	}
	else if (dir == VMMAP_DIR_LOHI)
	{
	if(free_sp - npages < lgap)
		{
		lgap = free_sp - npages;
		} 
	}	
	}list_iterate_end();
	if(dir == VMMAP_DIR_HILO)
	{
	return gap;
	}
	else if(dir == VMMAP_DIR_LOHI)
	{
	return lgap;
	}
       /* NOT_YET_IMPLEMENTED("VM: vmmap_find_range");*/
        else 
	{
	return -1;
	}
}

/* Find the vm_area that vfn lies in. Simply scan the address space
 * looking for a vma whose range covers vfn. If the page is unmapped,
 * return NULL. */
vmarea_t *
vmmap_lookup(vmmap_t *map, uint32_t vfn)
{	
	/*--------------Ishita-----------------*/
	vmarea_t *vma;
	KASSERT(NULL != map);
	dbg(DBG_PRINT,"vmmap_lookup begins\n");
	list_iterate_begin(&map->vmm_list,vma,vmarea_t,vma_plink){
		if(vfn >= vma->vma_start && vfn <= vma->vma_end){
			
	dbg(DBG_PRINT,"vmmap_lookup : returning vma\n");
		return vma;
		}	
	
	dbg(DBG_PRINT,"vmmap_lookup ends\n");
	}list_iterate_end();
	
/*        NOT_YET_IMPLEMENTED("VM: vmmap_lookup");*/
        return NULL;
	/*---------------------------------------*/
}

/* Allocates a new vmmap containing a new vmarea for each area in the
 * given map. The areas should have no mmobjs set yet. Returns pointer
 * to the new vmmap on success, NULL on failure. This function is
 * called when implementing fork(2). */
vmmap_t *
vmmap_clone(vmmap_t *map)
{
        NOT_YET_IMPLEMENTED("VM: vmmap_clone");
        return NULL;
}

/* Insert a mapping into the map starting at lopage for npages pages.
 * If lopage is zero, we will find a range of virtual addresses in the
 * process that is big enough, by using vmmap_find_range with the same
 * dir argument.  If lopage is non-zero and the specified region
 * contains another mapping that mapping should be unmapped.
 *
 * If file is NULL an anon mmobj will be used to create a mapping
 * of 0's.  If file is non-null that vnode's file will be mapped in
 * for the given range.  Use the vnode's mmap operation to get the
 * mmobj for the file; do not assume it is file->vn_obj. Make sure all
 * of the area's fields except for vma_obj have been set before
 * calling mmap.
 *
 * If MAP_PRIVATE is specified set up a shadow object for the mmobj.
 *
 * All of the input to this function should be valid (KASSERT!).
 * See mmap(2) for for description of legal input.
 * Note that off should be page aligned.
 *
 * Be very careful about the order operations are performed in here. Some
 * operation are impossible to undo and should be saved until there
 * is no chance of failure.
 *
 * If 'new' is non-NULL a pointer to the new vmarea_t should be stored in it.
 */
int
vmmap_map(vmmap_t *map, vnode_t *file, uint32_t lopage, uint32_t npages,
          int prot, int flags, off_t off, int dir, vmarea_t **new)
{
	KASSERT(NULL != map);
	KASSERT(0 < npages);
	 KASSERT(!(~(PROT_NONE | PROT_READ | PROT_WRITE | PROT_EXEC) & prot));
	KASSERT((MAP_SHARED & flags) || (MAP_PRIVATE & flags));
	KASSERT((0 == lopage) || (ADDR_TO_PN(USER_MEM_LOW) <= lopage));
	KASSERT((0 == lopage) || (ADDR_TO_PN(USER_MEM_HIGH) >= (lopage + npages)));	
	KASSERT(PAGE_ALIGNED(off));
	dbg(DBG_PRINT,"Inside vmmap_map\n");
	

	vmarea_t * vma;
	mmobj_t **ret, *ano;
	if(lopage == 0)
		{
		int gap = vmmap_find_range(map, npages, dir);
		}
	else
		{
		
		dbg_print("Starting to map : checking file nulll\n");
		if(file == NULL)
		{
		dbg_print("Starting to map : file is null\n");
		ano = anon_create();
		if(vmmap_is_range_empty(map, lopage, npages) == 1)
			{
			vma = vmmap_lookup(map, lopage);
			dbg_print("Ishita: my lopage value in vmmap = %d\n",lopage);
			vma = vmarea_alloc();
                        vma->vma_start = lopage;
			dbg_print("Ishita: my lopage value in vmmap = %d\n",lopage);
                        vma->vma_end = lopage + npages;
                        vma->vma_prot = prot;
                        vma->vma_flags = flags;
			vma->vma_obj = ano;
			new = &vma;
			 vmmap_insert(map, vma);	
			dbg(DBG_PRINT,"After the anon obj is created fine\n");
			}
		else	
			{
			int errno = vmmap_remove(map, lopage, npages);
                        vma = vmmap_lookup(map, lopage);
                        vma = vmarea_alloc();
                        vma->vma_start = lopage;
                        vma->vma_end = lopage + npages;
                        vma->vma_prot = prot;
                        vma->vma_flags = flags;

			}
		}
		else
		{
		if(vmmap_is_range_empty(map, lopage, npages)==1)
			{
			
			dbg_print("Entering if loop for file is not NULL %d\n",lopage);
			vma=vmmap_lookup(map, lopage);
			vma = vmarea_alloc();
			vma->vma_start = lopage;
			vma->vma_end = lopage + npages;
			vma->vma_prot = prot;
			vma->vma_flags = flags;
			dbg_print("After lookup : inside if loop\n");
			KASSERT(vma !=NULL && "Check for VMA failed\n");
			int errno = file->vn_ops->mmap(file, vma,&(vma->vma_obj));
				
			dbg_print("After file->mmap operation: \n");
			file->vn_mmobj =*vma->vma_obj;
			new =&vma;
		vmmap_insert(map, vma);
			dbg_print("Exiting if loop for file is not NULL\n");
			}
		else
			{
			dbg_print("Entering if loop for file is not NULL and \
			range is not empty\n");
			int errno = vmmap_remove(map, lopage, npages);
			vma = vmmap_lookup(map, lopage);
			vma = vmarea_alloc();
                        vma->vma_start = lopage;
                        vma->vma_end = lopage + npages;
                        vma->vma_prot = prot;
                        vma->vma_flags = flags;
			int err = file->vn_ops->mmap(file,vma,&(vma->vma_obj));
			file->vn_mmobj =* vma->vma_obj;
			new = &vma;
			}		
	}	}

/*        NOT_YET_IMPLEMENTED("VM: vmmap_map");*/
        return 0;
}

/*
 * We have no guarantee that the region of the address space being
 * unmapped will play nicely with our list of vmareas.
 *
 * You must iterate over each vmarea that is partially or wholly covered
 * by the address range [addr ... addr+len). The vm-area will fall into one
 * of four cases, as illustrated below:
 *
 * key:
 *          [             ]   Existing VM Area
 *        *******             Region to be unmapped
 *
 * Case 1:  [   ******    ]
 * The region to be unmapped lies completely inside the vmarea. We need to
 * split the old vmarea into two vmareas. be sure to increment the
 * reference count to the file associated with the vmarea.
 *
 * Case 2:  [      *******]**
 * The region overlaps the end of the vmarea. Just shorten the length of
 * the mapping.
 *
 * Case 3: *[*****        ]
 * The region overlaps the beginning of the vmarea. Move the beginning of
 * the mapping (remember to update vma_off), and shorten its length.
 *
 * Case 4: *[*************]**
 * The region completely contains the vmarea. Remove the vmarea from the
 * list.
 */
int
vmmap_remove(vmmap_t *map, uint32_t lopage, uint32_t npages)
{
/****************Aahlad ***********************/
	vmarea_t *vma,*nw_vma;

	/* Check that there is something to remove */
	if (vmmap_is_range_empty(map,lopage,npages))
	{
		return -1;
	}

	nw_vma = vmarea_alloc();
	list_iterate_begin(&map->vmm_list, vma, vmarea_t, vma_plink){
	/******Case 1*************/
	
	if(lopage > vma->vma_start && lopage+npages-1 < vma->vma_end)
	{
		/*update existing area end*/
		vma->vma_end = lopage -1;

		/*handle offset for new area*/
		nw_vma->vma_off += vma->vma_start - (lopage + npages);
		nw_vma->vma_start = lopage + npages;
		nw_vma->vma_end = vma->vma_end;

		/*handle two areas pointing to same object*/
		vma->vma_obj->mmo_refcount++;
		nw_vma->vma_obj = vma->vma_obj;	
		vmmap_insert(map, nw_vma);	
	}

/***********************Case 2 **********************/
	if( lopage > vma->vma_start && lopage <= vma->vma_end && lopage + npages - 1 >= vma->vma_end)
	{
		vma->vma_end = lopage -1;
	}

/***************Case 3 ************************/

	if(lopage <= vma->vma_start && (lopage + npages -1) >= vma->vma_start && (lopage + npages -1) < vma->vma_end)
	{
		vma->vma_off += vma->vma_start - (lopage + npages);
		vma->vma_start = lopage + npages;
	}
/**************Case 4 **********************/
	if(lopage <= vma->vma_start && (lopage + npages -1) <= vma->vma_end)
	{
		list_remove(&vma->vma_plink);
		vmarea_free(vma);
	} 
	}list_iterate_end();

        return 0;
/**************************************************/
}

/*
 * Returns 1 if the given address space has no mappings for the
 * given range, 0 otherwise.
 */
int
vmmap_is_range_empty(vmmap_t *map, uint32_t startvfn, uint32_t npages)
{

uint32_t endvfn = startvfn + npages; 
KASSERT((startvfn < endvfn) && (ADDR_TO_PN(USER_MEM_LOW) <= startvfn) && (ADDR_TO_PN(USER_MEM_HIGH) >= endvfn));
	dbg(DBG_PRINT,"Kernel3:SysMrg: entering vmmap_is_range_empty\n");
	KASSERT( npages > 0 && "npages cant be zero, go fix something");
        vmarea_t *vma;

	/* Handle basic case of list empty (no areas) */
	if(list_empty(&map->vmm_list)){
		
       		dbg_print("Kernel3:SysMsg: exiting the vmmap_is_range_empty with the return value = 1 \n");
		return 1;
	}  

	/* look for area in range*/
	/* Case 1: start is in an area */
	/* Case 2: end is in an area */
	/* Case 3: start and end span an area */
      	list_iterate_begin(&map->vmm_list, vma, vmarea_t, vma_plink){
        	if (vma->vma_start >= startvfn && startvfn <= vma->vma_end) 
		{
			return 0;
		} 
		else if (vma->vma_start >= (startvfn + npages - 1) && (startvfn + npages -1) <= vma->vma_end)
		{
			return 0;
		}
		else if (startvfn <= vma->vma_start && (startvfn + npages - 1) >= vma->vma_end)
		{
			return 0;
		}
        }list_iterate_end();


	/* no mapping found in search */
       	dbg(DBG_PRINT,"Kernel3:SysMsg: exiting the vmmap_is_range_empty with the return value = 1 \n");
	return 1; 

}

/* Read into 'buf' from the virtual address space of 'map' starting at
 * 'vaddr' for size 'count'. To do so, you will want to find the vmareas
 * to read from, then find the pframes within those vmareas corresponding
 * to the virtual addresses you want to read, and then read from the
 * physical memory that pframe points to. You should not check permissions
 * of the areas. Assume (KASSERT) that all the areas you are accessing exist.
 * Returns 0 on success, -errno on error.
 */
int
vmmap_read(vmmap_t *map, const void *vaddr, void *buf, size_t count)
{
        NOT_YET_IMPLEMENTED("VM: vmmap_read");
        return 0;
}

/* Write from 'buf' into the virtual address space of 'map' starting at
 * 'vaddr' for size 'count'. To do this, you will need to find the correct
 * vmareas to write into, then find the correct pframes within those vmareas,
 * and finally write into the physical addresses that those pframes correspond
 * to. You should not check permissions of the areas you use. Assume (KASSERT)
 * that all the areas you are accessing exist. Remember to dirty pages!
 * Returns 0 on success, -errno on error.
 */
int
vmmap_write(vmmap_t *map, void *vaddr, const void *buf, size_t count)
{
	dbg_print("Before write operation\n");
	uint32_t vfn;
        
        pframe_t **result;
        vmarea_t *vma,**new;
	vfn = ADDR_TO_PN(vaddr);
        vma = vmmap_lookup(map, vfn);

		if(vma == NULL){
		dbg_print("entered into vma is  NULL\n");
	
	vmmap_map(map, NULL , vfn, count,MAP_SHARED,PROT_WRITE,0,VMMAP_DIR_HILO,new);
}

dbg_print("before pframe_get operation \n");
	KASSERT(vma !=NULL && "vma failed\n");
	
        int errno = pframe_get(vma->vma_obj,vfn,result);

	dbg_print("before memcpy operation \n");
	memcpy(result, buf, count);
	dbg_print("After write operation \n");
	        /*NOT_YET_IMPLEMENTED("VM: vmmap_write");*/
        return 0;
}

/* a debugging routine: dumps the mappings of the given address space. */
size_t
vmmap_mapping_info(const void *vmmap, char *buf, size_t osize)
{
        KASSERT(0 < osize);
        KASSERT(NULL != buf);
        KASSERT(NULL != vmmap);

        vmmap_t *map = (vmmap_t *)vmmap;
        vmarea_t *vma;
        ssize_t size = (ssize_t)osize;

        int len = snprintf(buf, size, "%21s %5s %7s %8s %10s %12s\n",
                           "VADDR RANGE", "PROT", "FLAGS", "MMOBJ", "OFFSET",
                           "VFN RANGE");

        list_iterate_begin(&map->vmm_list, vma, vmarea_t, vma_plink) {
                size -= len;
                buf += len;
                if (0 >= size) {
                        goto end;
                }


                len = snprintf(buf, size,
                               "%#.8x-%#.8x  %c%c%c  %7s 0x%p %#.5x %#.5x-%#.5x\n",
                               vma->vma_start << PAGE_SHIFT,
                               vma->vma_end << PAGE_SHIFT,
                               (vma->vma_prot & PROT_READ ? 'r' : '-'),
                               (vma->vma_prot & PROT_WRITE ? 'w' : '-'),
                               (vma->vma_prot & PROT_EXEC ? 'x' : '-'),
                               (vma->vma_flags & MAP_SHARED ? " SHARED" : "PRIVATE"),
                               vma->vma_obj, vma->vma_off, vma->vma_start, vma->vma_end);
        } list_iterate_end();

end:
        if (size <= 0) {
                size = osize;
                buf[osize - 1] = '\0';
        }
        /*
        KASSERT(0 <= size);
        if (0 == size) {
                size++;
                buf--;
                buf[0] = '\0';
        }
        */
        return osize - size;
}
